﻿using System;
using System.Collections.Generic;

using System.Text;
using System.ComponentModel;
using System.Web.UI;
using TA.Turbo.Design;

namespace TA.Turbo.Controls
{
	/// <summary>
	/// Validates a control by comparing its value using one of the available comparison operators.
	/// </summary>
	public class CompareValidator : BaseValidator
	{
		#region PUBLIC
		/// <summary>
		/// Ctor.
		/// </summary>
		public CompareValidator()
		{
		}
		#region PROPS
		#region DEF
		/// <summary>
		/// Props.
		/// </summary>
		protected static new Properties _clientProperties = Define(BaseValidator._clientProperties,
			new BasePropertyDefinition[] {
				new EnumPropertyDefinition<ValidationCompareOperator>("Operator", "O", ValidationCompareOperator.EqualText),
				new PropertyDefinition<bool>("Negated", "N", false),
				new EnumPropertyDefinition<ValidationDataType>("Type", "Ty", ValidationDataType.String),
				new PropertyDefinition<string>("ComparisonValue1", "CV1", null),
				new PropertyDefinition<string>("ComparisonValue2", "CV2", null),
				new ControlIdPropertyDefinition("ComparisonControl1Id", "CC1", null),
				new ControlIdPropertyDefinition("ComparisonControl2Id", "CC2", null),
				new EnumPropertyDefinition<ValidationValueUsage>("MainValueUsage", "MV", ValidationValueUsage.FirstValue),
				new EnumPropertyDefinition<ValidationValueUsage>("SecondValueUsage", "SV", ValidationValueUsage.SecondValue),
			});
		/// <summary>
		/// Props.
		/// </summary>
		public override Properties ClientProperties
		{
			get
			{
				return _clientProperties;
			}
		}
		#endregion
		/// <summary>
		/// The comparison operator to use when validating the value.
		/// </summary>
		[DefaultValue(ValidationCompareOperator.EqualText)]
		public ValidationCompareOperator Operator
		{
			get
			{
				return GetProperty<ValidationCompareOperator>("Operator");
			}
			set
			{
				ViewState["Operator"] = value;
			}
		}
		/// <summary>
		/// Negates the operator. In case of equality or text containing operators, works as a logical NOT. 
		/// In case of comparison operators (such as GreaterThan or BetweenEqual) changes the direction of comparison; the result is not the negation of the result of the original operator but is determined by applying an opposite direction operator.
		/// For example, GreaterThanEqual validates 5 versus 5 and so does "NOT" GreaterThanEqual - because it effectively becomes "LessThanEqual".
		/// </summary>
		[DefaultValue(false)]
		public bool Negated
		{
			get
			{
				return this.GetProperty<bool>("Negated");
			}
			set
			{
				this.ViewState["Negated"] = value;
			}
		}
		/// <summary>
		/// Type of the value to be validated.
		/// </summary>
		[DefaultValue(ValidationDataType.String)]
		public ValidationDataType Type
		{
			get
			{
				return GetProperty<ValidationDataType>("Type");
			}
			set
			{
				ViewState["Type"] = value;
			}
		}
		/// <summary>
		/// The first comparison value. 
		/// </summary>
		[DefaultValue("")]
		public string ComparisonValue1
		{
			get
			{
				return GetProperty<string>("ComparisonValue1");
			}
			set
			{
				ViewState["ComparisonValue1"] = value;
			}
		}
		/// <summary>
		/// The second comparison value.
		/// </summary>
		[DefaultValue("")]
		public string ComparisonValue2
		{
			get
			{
				return GetProperty<string>("ComparisonValue2");
			}
			set
			{
				ViewState["ComparisonValue2"] = value;
			}
		}
		/// <summary>
		/// The ID of the control whose value is to be used as the first comparison value.
		/// </summary>
		[IDReferenceProperty]
		[TypeConverter(typeof(ControlIdConverter<ValueDisplayControlIdLister>))]
		[DefaultValue("")]
		[MergableProperty(false)]
		public string ComparisonControl1Id
		{
			get
			{
				return GetProperty<string>("ComparisonControl1Id");
			}
			set
			{
				ViewState["ComparisonControl1Id"] = value;
			}
		}
		/// <summary>
		/// The control whose value is to be used as the first comparison value.
		/// </summary>
		[Browsable(false)]
		[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]		
		public BaseValueDisplayControl ComparisonControl1
		{
			get
			{
				return this.FindControlGlobal(ComparisonControl1Id) as BaseValueDisplayControl;
			}
		}
		/// <summary>
		/// The ID of the control whose value is to be used as the second comparison value.
		/// </summary>
		[IDReferenceProperty]
		[TypeConverter(typeof(ControlIdConverter<ValueDisplayControlIdLister>))]
		[DefaultValue("")]
		[MergableProperty(false)]
		public string ComparisonControl2Id
		{
			get
			{
				return GetProperty<string>("ComparisonControl2Id");
			}
			set
			{
				ViewState["ComparisonControl2Id"] = value;
			}
		}
		/// <summary>
		/// The control whose value is to be used as the second comparison value.
		/// </summary>
		[Browsable(false)]
		[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
		public BaseValueDisplayControl ComparisonControl2
		{
			get
			{
				return this.FindControlGlobal(ComparisonControl2Id) as BaseValueDisplayControl;
			}
		}
		/// <summary>
		/// Usage option for the main comparison value. This value will be the only one used in most of the comparison operations, except for Between and BetweenEqual, where the second value will also be used.
		/// </summary>
		[DefaultValue(ValidationValueUsage.FirstValue)]
		public ValidationValueUsage MainValueUsage
		{
			get
			{
				return GetProperty<ValidationValueUsage>("MainValueUsage");
			}
			set
			{
				ViewState["MainValueUsage"] = value;
			}
		}
		/// <summary>
		/// Usage option for the secondary comparsion value. This value will only be used in Between and BetweenEqual operations to specify the end of the range.
		/// </summary>
		[DefaultValue(ValidationValueUsage.SecondValue)]
		public ValidationValueUsage SecondValueUsage
		{
			get
			{
				return GetProperty<ValidationValueUsage>("SecondValueUsage");
			}
			set
			{
				ViewState["SecondValueUsage"] = value;
			}
		}
		#endregion
		#region METHODS
		#endregion
		#endregion
		#region INTERNAL
		#region METHODS
		#region INIT
		/// <summary>
		/// Registers "CompareValidator.js".
		/// </summary>
		protected internal override void RegisterIncludes(IncludeCollection includes)
		{
			base.RegisterIncludes(includes);

			includes.Add(new ScriptResourceInclude("TA.Turbo.Res.JS.Validators.CompareValidator.js", typeof(CompareValidator)));
		}
		//protected override void AddAttributesToRender(System.Web.UI.HtmlTextWriter writer)
		//{
		//  //writer.AddAttribute("Operator", ((int)Operator).ToString());
		//  //writer.AddAttribute("Type", ((int)Type).ToString());
		//  //writer.AddAttribute("ComparisonValue1", ComparisonValue1);
		//  //writer.AddAttribute("ComparisonValue2", ComparisonValue2);
		//  //if (ComparisonControl1 != null)
		//  //  writer.AddAttribute("ComparisonControl1Id", ComparisonControl1.ClientID);
		//  //if (ComparisonControl2 != null)
		//  //  writer.AddAttribute("ComparisonControl2Id", ComparisonControl2.ClientID);
		//  //writer.AddAttribute("MainValueUsage", ((int)MainValueUsage).ToString());
		//  //writer.AddAttribute("SecondValueUsage", ((int)SecondValueUsage).ToString());

		//  base.AddAttributesToRender(writer);
		//}
		#endregion
		#region VALIDATION
		private object ConvertToOurType(object v)
		{
			switch (Type)
			{
				case ValidationDataType.String:
					return (v ?? "").ToString();
				case ValidationDataType.Integer:
					return Convert.ToInt64((v ?? "").ToString());
				case ValidationDataType.Double:
					return Convert.ToDouble((v ?? "").ToString());
				case ValidationDataType.Date:
					return Convert.ToDateTime((v ?? "").ToString());
				case ValidationDataType.Currency:

					return Convert.ToDecimal(RemoveCurrencySymbols(RemoveGroupingSymbols((v ?? "").ToString())));
			}
			throw new NotImplementedException();
		}
		private string RemoveCurrencySymbols(string p)
		{
			return p.Replace("$", "");
		}
		private string RemoveGroupingSymbols(string p)
		{
			return p.Replace(",", "");
		}
		private object GetComparisonValue(ValidationValueUsage valueUsage)
		{
			switch (valueUsage)
			{
				case ValidationValueUsage.FirstValue:
					return ComparisonValue1;
				case ValidationValueUsage.SecondValue:
					return ComparisonValue2;
				case ValidationValueUsage.FirstControl:
					return ComparisonControl1 == null ? null : ComparisonControl1.GenericValue;
				case ValidationValueUsage.SecondControl:
					return ComparisonControl2 == null ? null : ComparisonControl2.GenericValue;
			}
			throw new ArgumentException("Unknown ValidationValueUsage.");
		}
		/// <summary>
		/// Performs the validation.
		/// </summary>
		protected override void DoValidate()
		{
			if ((Operator == ValidationCompareOperator.Contains || Operator == ValidationCompareOperator.IsContained)
				&& Type != ValidationDataType.String)
				throw new InvalidOperationException("Unable to apply 'contain' operators to data types other than string.");

			SetValidStatus(false);

			object value1, value2 = null, value;
			try
			{
				switch (Operator)
				{
					case ValidationCompareOperator.Equal:
						value1 = ConvertToOurType(GetComparisonValue(MainValueUsage));
						value = ConvertToOurType(ValidatedValue);
						SetValidStatus(object.Equals(value, value1) ^ this.Negated);
						break;
					case ValidationCompareOperator.EqualText:
						value1 = GetComparisonValue(MainValueUsage);
						value = ValidatedValue;
						SetValidStatus(string.Equals((value ?? "").ToString(), (value1 ?? "").ToString()) ^ this.Negated);
						break;
					case ValidationCompareOperator.GreaterThan:
					case ValidationCompareOperator.GreaterThanEqual:
					case ValidationCompareOperator.Between:
					case ValidationCompareOperator.BetweenOrEqual:
						value1 = ConvertToOurType(GetComparisonValue(MainValueUsage));
						value = ConvertToOurType(ValidatedValue);

						int cr1 = ((IComparable)value).CompareTo(value1), cr2 = 0;

						if (Operator == ValidationCompareOperator.Between || Operator == ValidationCompareOperator.BetweenOrEqual)
						{
							value2 = ConvertToOurType(GetComparisonValue(SecondValueUsage));
							cr2 = ((IComparable)value).CompareTo(value2);
						}
						switch (Operator)
						{
							case ValidationCompareOperator.GreaterThan:
								if (this.Negated)
									SetValidStatus(cr1 < 0);
								else
									SetValidStatus(cr1 > 0);
								break;
							case ValidationCompareOperator.GreaterThanEqual:
								if (this.Negated)
									SetValidStatus(cr1 <= 0);
								else
									SetValidStatus(cr1 >= 0);
								break;

							case ValidationCompareOperator.Between:
								if (this.Negated)
									SetValidStatus(cr1 < 0 || cr2 > 0);
								else
									SetValidStatus(cr1 > 0 && cr2 < 0);
								break;
							case ValidationCompareOperator.BetweenOrEqual:
								if (this.Negated)
									SetValidStatus(cr1 <= 0 || cr2 >= 0);
								else
									SetValidStatus(cr1 >= 0 && cr2 <= 0);
								break;
						}

						break;
					case ValidationCompareOperator.Contains:
					case ValidationCompareOperator.IsContained:
						string sVal = (ValidatedValue ?? "").ToString()
							, sVal1 = (GetComparisonValue(MainValueUsage) ?? "").ToString();

						switch (Operator)
						{
							case ValidationCompareOperator.Contains:
								SetValidStatus(sVal.IndexOf(sVal1, StringComparison.CurrentCultureIgnoreCase) != -1 ^ this.Negated);
								break;
							case ValidationCompareOperator.IsContained:
								SetValidStatus(sVal1.IndexOf(sVal, StringComparison.CurrentCultureIgnoreCase) != -1 ^ this.Negated);
								break;
						}
						break;
					case ValidationCompareOperator.DataTypeCheck:
						try
						{
							value = ConvertToOurType(ValidatedValue);
							SetValidStatus(!this.Negated);
						}
						catch
						{
							SetValidStatus(this.Negated);
						}
						break;
				}
			}
			catch
			{
				SetValidStatus(false);
			}
		}
		#endregion
		#endregion
		#endregion
	}
}
